#!/gsa/tucgsa/projects/e/essni/usr/aix5.1/bin/python
import os,sys
import getpass
import subprocess as subp
from cStringIO import StringIO
import difflib
import tarfile,zipfile
import time
from UserDict import UserDict
from datetime import datetime

from pygments import highlight
from pygments.formatters import HtmlFormatter
from pygments.lexers import guess_lexer_for_filename


CMVC_HOME='/usr/cmvc'

CMVCLOG_BIN=CMVC_HOME+'/cmvclog'
FILE_BIN=CMVC_HOME+'/File'
REPORT_BIN=CMVC_HOME+'/Report'

os.environ['CMVC_AUTH_METHOD']='PW'
os.environ['CMVC_FAMILY']='mc@mc.18m.com@1202'

def cmvclogin(user, pw):
    cmd = CMVCLOG_BIN + ' -in %s -noprompt "%s"' % (user, pw)
    subp.check_call(cmd, shell=True)

def extractfile(release, pathName , SID):
    cmd = FILE_BIN + ''' -extract %s -release %s -version %s -stdout''' % (pathName, release, SID)
    print cmd
    o = subp.check_output(cmd, shell=True)
    return o

def getpreversion(release, pathName, SID):
    cmd = REPORT_BIN + ''' -general Versions -select "SID, changeDate" -where "id in (select previousId from Versions where id in (select versionId from ChangeView c where c.releaseName='%s' and c.pathName='%s' and c.versionSID='%s'))"''' % (release, pathName, SID)
    print cmd
    o = subp.check_output(cmd, shell=True)
    # return 
    preSID, changeDate = o.split('\n')[0].strip().split('|')
    changeDate = time.mktime(time.strptime(changeDate, '%Y/%m/%d %H:%M:%S'))
    return preSID, changeDate

def getChangeSet(release, defect):
    cmd = REPORT_BIN + ''' -general ChangeView -select "TYPE,VERSIONSID,PATHNAME,addDate" -where "DEFECTNAME in (\'%s\') and RELEASENAME = '%s' and TYPE!='delete' order by PATHNAME asc, VERSIONID asc "''' % (defect, release)
    print cmd
    o = os.popen(cmd).read()
    changes_txt = []
    for l in o.split('\n'):
        if len(l.strip())>0:
            changes_txt.append(l)
        
    return changes_txt

def parseChangeSet(changes_txt, userinput):
    now = time.time()
    changes = []
    for i in changes_txt:
        chg = UserDict()
        if userinput:
            # user input change set has no addDate
            chg.type, chg.SID, chg.pathName = i.strip().split()
            chg.addDate = now
        else:
            chg.type, chg.SID, chg.pathName, addDate = i.strip().split('|')
            chg.addDate = time.mktime(time.strptime(addDate, '%Y/%m/%d %H:%M:%S'))
        if chg.type == 'link':
            continue #ignore link
        changes.append(chg)
    return changes
    
def generate_diff_pkg(release, defect, changes, compressor):
    now = time.time()
    formatter=HtmlFormatter(encoding='utf-8')
    css=formatter.get_style_defs()
    lindex = []
    fzip = StringIO()
    zip = compressor(file=fzip, name=defect + '.' + compressor.getFilenameExt())
    for chg in changes:
        # strip out type of link
        if chg.type == 'create':
            chg.preSID = None
            chg.oldchangedate = now
        else: # chg.type == 'delta':
            chg.preSID, chg.oldchangedate = getpreversion(release, chg.pathName, chg.SID)
        
        txtold,txtnew,html = get_diff(chg.pathName, release, chg.preSID, chg.SID,  formatter, css)
        
        dn = os.path.dirname(chg.pathName)
        bn = os.path.basename(chg.pathName)
        
        chg.oldfilename = '%s/%s,%s'%(dn, chg.preSID, bn)
        chg.newfilename = '%s/%s,%s'%(dn, chg.SID, bn)
        chg.difffilename = '%s-%s-%s.html'%(chg.pathName, chg.preSID, chg.SID)
        # old file
        oti = zip.make_info(defect + '/' + chg.oldfilename, len(txtold), chg.oldchangedate)
        zip.add(oti, txtold)
        # new file
        nti = zip.make_info(defect + '/' + chg.newfilename, len(txtnew), chg.addDate)
        zip.add(nti, txtnew)
        # diff html
        hti = zip.make_info(defect + '/' + chg.difffilename, len(html), now)
        zip.add(hti, html)
        
    
    txtindex = format_index_page(release, defect, changes)
    iti = zip.make_info(defect + '/' + 'index.html', len(txtindex), now)
    zip.add(iti, txtindex)
    
    zip.close()
    
    return fzip.getvalue()

def get_diff(pathName, release, preSID, SID, formatter, css):
    if not preSID:
        txtold = ''
    else:
        txtold = extractfile(release, pathName, preSID)
    
    txtnew = extractfile(release, pathName, SID)
    
    fold = StringIO(txtold)
    fnew = StringIO(txtnew)
    
    htmlDfer = HtmlDiff(pathName, formatter, css)
    html = htmlDfer.make_file(fold,
                              fnew,
                              fromdesc=preSID,
                              todesc=SID,
                              context=False,
                              encoding='utf-8',
                              indexpath=getindexpath(pathName),
                              comment='%s  %s-->%s'%(pathName, preSID, SID))
    return txtold, txtnew, html

def getindexpath(pathName):
    return '../' * pathName.count('/') + 'index.html'
    
def format_index_page(release, defect, changes):
    lfiles = []
    lfiles.append("%s Code change in :%s" % (release, defect))
    lfiles.append("<table border=1>")
    line = '<tr><th>type</th><th>SID</th><th>pathName</th></tr>'
    for chg in changes:
        line = '<tr><td>%s</td><td>%s</td><td><a href="%s">%s</a></td>' % \
        (chg.type, chg.SID, chg.difffilename, chg.pathName)
        lfiles.append(line)
    lfiles.append("</table>")
    txtindex = '\n'.join(lfiles)
    return txtindex

class HtmlDiff(difflib.HtmlDiff):
    def __init__(self, pathName, formatter, css):
        super(HtmlDiff, self).__init__()
        self.lexer = guess_lexer_for_filename(pathName, '')
        self.formatter = formatter
        self.css = css
    def _format_line(self,side,flag,linenum,text):
        try:
            linenum = '%d' % linenum
            id = ' id="%s%s"' % (self._prefix[side],linenum)
            if flag:
                # replace those things that would get confused with HTML symbols
                text=text.replace("&","&amp;").replace(">","&gt;").replace("<","&lt;")

                # make space non-breakable so they don't get compressed or line wrapped
                text = text.replace(' ','&nbsp;').rstrip()
            else:
                text = highlight(text, self.lexer, self.formatter)
        except TypeError:
            # handle blank lines where linenum is '>' or ''
            id = ''

        return '<td class="diff_header"%s>%s</td><td nowrap="nowrap">%s</td>' \
               % (id,linenum,text)
    def make_file(self,fromlines,tolines,fromdesc='',todesc='',context=False,
                  numlines=5, encoding='utf-8', indexpath='index.html', comment=""):
        """Returns HTML file of side by side comparison with change highlights
        
        Arguments:
        fromlines -- list of "from" lines
        tolines -- list of "to" lines
        fromdesc -- "from" file column header string
        todesc -- "to" file column header string
        context -- set to True for contextual differences (defaults to False
            which shows full differences).
        numlines -- number of context lines.  When context is set True,
            controls number of lines displayed before and after the change.
            When context is False, controls the number of lines to place
            the "next" link anchors before the next change (so click of
            "next" link jumps to just before the change).
        """
        return self._file_template % dict(
            encoding = encoding,
            indexpath = indexpath,
            comment = comment,
            styles = self._styles + '\n' + self.css,
            legend = self._legend,
            table = self.make_table(fromlines,tolines,fromdesc,todesc,
                                    context=context,numlines=numlines))
    
    _file_template = """
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html>

<head>
    <meta http-equiv="Content-Type"
        content="text/html; charset=%(encoding)s" />
    <title></title>
    <style type="text/css">%(styles)s
    </style>
</head>
<body style="background-image:url(about:blank);background-attachment:fixed;">
<div style="position:fixed; top:0px; left:0px; height:30px; width:100%%; background-color:#FFF;
    _position:absolute;
    *top:expression(eval(document.compatMode &&
                         document.compatMode=='CSS1Compat') ?
                    documentElement.scrollTop
                    : document.body.scrollTop ); "><a href="%(indexpath)s">back to index</a>
<span style="margin-left: 50px">%(comment)s</span></div>
<div style=" height:30px; width:100%%"></div>
    %(table)s%(legend)s
</body>

</html>"""
# ----------------------------------------------------------------------------------------------
class Compresser():
    def __init__(self, file, name):
        raise Exception('abstract class')
class gzCompressor():
    @staticmethod
    def getFilenameExt():
        return "tgz"
    def __init__(self, file, name):
        self.tar = tarfile.open(name=name, mode='w:gz', fileobj=file)
    def add(self, info, str):
        self.tar.addfile(info, StringIO(str))
    def close(self):
        self.tar.close()
    def make_info(self, fn, size, mtime):
        """make a TarInfo object
        """
        ti = tarfile.TarInfo()
        ti.name = fn
        ti.size = size
        ti.mtime = mtime
        ti.mode = 0644
        ti.type = tarfile.REGTYPE
        return ti

class zipCompressor():
    @staticmethod
    def getFilenameExt():
        return "zip"
    def __init__(self, file, name):
        self.zip = zipfile.ZipFile(file=file, mode='w')
    def add(self, info, str):
        self.zip.writestr(info, str)
    def close(self):
        self.zip.close()
    def make_info(self, fn, size, mtime):
        """make a ZipInfo object
        """
        zi = zipfile.ZipInfo()
        zi.filename = fn
        zi.file_size = size
        zi.date_time = time.gmtime(mtime)
        return zi
    

def main():
    if True:
        user = raw_input('CMVC user [default shaofsh]:') or 'shaofsh'
        pw = getpass.getpass(prompt='CMVC pw:')
        release = raw_input("release [default R15f.CPSS]:") or "R15f.CPSS"
        defect = raw_input("defect #:")
        ownchangeset = (raw_input("use your own change set(y/n)? [default n]:") or 'n') == 'y'
        if ownchangeset:
            print 'paste change log from jShowDefect(end by empty line):'
            changeset = []
            sentinel = '' # ends when this string is seen
            for line in iter(raw_input, sentinel):
                changeset.append(line) # do things here
        try:
            cmvclogin(user, pw)
            if not ownchangeset:
                changeset = getChangeSet(release, defect)
            changes = parseChangeSet(changeset, ownchangeset)
            compressor = zipCompressor
            print 'generate_diff_pkg'
            tgz = generate_diff_pkg(release, defect, changes, compressor)
            
            filename = '%s-%s' % (defect, release) + '.' + compressor.getFilenameExt()
            open(filename, 'wb').write(tgz)
        except subp.CalledProcessError, e:
            print 'subp error:'
            print 'command:' + e.cmd
            print 'output:' + str(e.output)
        return 0
    
if __name__ == "__main__":
    sys.exit(main())
